<?php
/*
 * @Author: your name
 * @Date: 2019-12-25 09:53:48
 * @LastEditTime: 2020-09-15 11:37:29
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /ahsmp_3/application/timeexecution/service/Netfilter/LearnData.php
 */
namespace app\timeexecution\service\Netfilter;


use app\api\model\learn_data\HostLearnDataModel;
use app\timeexecution\service\Base;
use think\Db;

class LearnData extends Base
{
    private $longSrcIp ;//源数字ip
    private $longDestIp ;//源数字ip
    private $DestPort ;//源数字ip
    private $hostId;//主机id
    private $macAddr;//需要添加的mac地址
    private $config_process;//配置中的处理方式
    private $config_have_mac;//配置中的是否有mac地址
    private $config_auto;//配置中规则下发模式
    private $learnUpdateData = ['matched' => 1];//学习数据 需要修改的字段
    private $clientRules = [] ;//发送到客户端的规则集
    private $fileName = 'netfilterProcessLearnData.lock';//文件名
    private $path = '../runtime/timexec/';//定时文件存放路径
    private $newHit = false;
    private $nowtime = 0;//当前时间
    private $max_process_number = 250;
    /**
     * @name: netfilter 学习数据
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-14 13:30:09
     */
    public function process ()
    {
        $this->checkNetfilter();
        $this->wirteLock($this->path, $this->fileName);
        $unmatched = $this->getUnmachedLearnData();
        $host_id_a = array_unique(array_column($unmatched, 'host_id'));
        $learnConfig = $this->getNetfilterConfig($host_id_a);

        $date = date('Y-m-d H:i:s');
        $this->nowtime = time();
        foreach ($learnConfig as $host_id => $config)
        {
            $this->newHit = false;
            $this->hostId = $host_id;//主机id
            $this->config_process = $config['allow'];//规则处理方式
            $this->config_have_mac = $config['have_mac_addr'];//是否存在mac地址
            $this->config_auto = $config['auto'];//学习规则下发模式
            $this->clientRules = [];//发送至客户端的 数据规则集
            file_put_contents('../runtime/autoRule.log', PHP_EOL . $date . ' 主机id: ' . $this->hostId . ' 未匹配学习数据条数: ' . count($unmatched) . ' 处理方式为: ' . $this->config_process . ' 是否保存mac地址: ' . $this->config_have_mac . ' 规则下发模式: ' . $this->config_auto , FILE_APPEND);
            foreach ($unmatched as $v)
            {
                if ($v['host_id'] == $this->hostId)
                {
                    $this->longSrcIp = ip2long($v['src_ip']);
                    $this->longDestIp = ip2long($v['dest_ip']);
                    $this->DestPort = $v['dest_port'];
                    $this->macAddr = $v['direction'] == 1 ? $v['dest_mac'] : $v['src_mac'];
                    $this->virtualRuleHit($v);
                    if ($this->config_auto == 2)
                    {
                        $this->netFilterRule($v);
                    }
                    $this->updateLearnTableData($v['id']);
                }
                
            }
            $this->addFidAndsendClient();
            $this->hitLogRemind();
        }

        $this->delLock($this->path . $this->fileName);
        
    }

    /**
     * @name: 规则发送包
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 15:37:40
     */
    public function addToClientRule ($rule)
    {

        $this->clientRules[] = [
            'alias' => 'AutoCreate'. date('Ymd') . $rule['id'] ,
            'direction' => $rule['direction'],
            'protocols' => $rule['protocols'],
            'process' => $this->config_process,
            'src_port' => 'any',
            'dest_port' => $rule['dest_port'],
            'src_ip' => $rule['src_ip'],
            'dest_ip' => $rule['dest_ip'],
            'mac_addr' => $this->macAddr,
            'status' => 1,
            'have_mac_addr' => $this->config_have_mac,
        ];

    }

    /**
     * @name: 添加到数据库的 规则包
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 15:42:47
     */
    public function addToSqlRule ($rule)
    {
        $return = [
            'host_id' => $this->hostId,
            'alias' => 'AutoCreate'. date('Ymd') . $rule['id'] ,
            'direction' => $rule['direction'],
            'protocols' => $rule['protocols'],
            'process' => $this->config_process,
            'status' => 1,
            'src_port_s' => 0,
            'src_port_e' => 65535,
            'dest_port_s' => $this->DestPort,
            'dest_port_e' => $this->DestPort,
            'src_ip_s' => $this->longSrcIp,
            'src_ip_e' => $this->longSrcIp,
            'dest_ip_s' => $this->longDestIp,
            'dest_ip_e' => $this->longDestIp,
            'type' => 2,
            'learn_id' => $rule['id'],
            'have_mac_addr' => $this->config_have_mac,
            'mac_addr' => $this->macAddr,
       ];

       return $return;
    }

    /**
     * @name: 虚拟规则命中
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 15:55:14
     */
    public function virtualRuleHit ($learnRule)
    {
        $rule_id_a = Db::name('VirtualRule')->field('id,alias')->where($this->longSrcIp . ' BETWEEN  src_ip_s AND src_ip_e')->where($this->longDestIp . ' BETWEEN dest_ip_s AND dest_ip_e')->where($this->DestPort . ' BETWEEN dest_port_s AND dest_port_e')->where('protocols', $learnRule['protocols'])->where('host_id', $this->hostId)->select();
        if($rule_id_a)
        {
            $hit_a = $hitLogData = [];
            foreach ($rule_id_a as $rule)
            {
                $this->newHit = true;
                $hit_a[] = $rule['id'];
                $data = [
                    'src_ip' => $learnRule['src_ip'],
                    'dest_ip' => $learnRule['dest_ip'],
                    'dest_port' => $learnRule['dest_port'],
                    'direction' => $learnRule['direction'],
                    'rule_id' => $rule['id'],
                    'rule_name' => $rule['alias'],
                    'host_id' => $this->hostId,
                    'ctime' => $this->nowtime,
                ];
                $hitLogData[] = "('". implode("','", $data) . "')";
            }
            $nowview = $learnRule['last_view'] == 0 ? 1 : $learnRule['view'] - $learnRule['last_view'];
            Db::name('VirtualRule')->where('host_id', $this->hostId)->whereIn('id', $hit_a)->setInc('hit', $nowview);
            if(!empty($hitLogData))
            {
                Db::name('NetfilterVirtualRuleHitLog')->query('INSERT IGNORE INTO ' . config('database.prefix') . 'netfilter_virtual_rule_hit_log (`src_ip`,`dest_ip`,`dest_port`,`direction`,`rule_id`,`rule_name`,`host_id`,`ctime`) VALUES ' . implode(',', $hitLogData));
            }
        }
    }

    /**
     * @name: 
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 15:59:29
     */
    public function netFilterRule ($rule)
    {
        $count = Db::name('HostRule')->where($this->longSrcIp . ' BETWEEN  src_ip_s AND src_ip_e')->where($this->longDestIp . ' BETWEEN dest_ip_s AND dest_ip_e')->where($this->DestPort . ' BETWEEN dest_port_s AND dest_port_e')->where('protocols', $rule['protocols'])->where('host_id', $this->hostId)->where('force', 0)->count();
        if ($count == 0)
        {
             $insert = $this->addToSqlRule($rule);
             Db::name('HostRule')->insert($insert);
             $this->addToClientRule($rule);
             $this->learnUpdateData['status'] = 1;
        }
    }

    /**
     * @name: 修改学习数据表
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 16:38:21
     */
    public function updateLearnTableData ($ruleId)
    {
        $this->learnUpdateData['last_view'] = Db::raw('view');
        $hostLearnDataModel = new HostLearnDataModel();
        $hostLearnDataModel->setHostId($this->hostId);
        $hostLearnDataModel->where('id', $ruleId)->update($this->learnUpdateData);
    }

    /**
     * @name: netfilter 规则添加fid
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 16:42:57
     */
    public function modifyFid()
    {
        Db::name('HostRule')->where('host_id', $this->hostId)->where('type', 2)->where('fid', 0)->update(['fid' => Db::raw('`id`')]);
    }

    /**
     * @name: 获取netfilter 主机信息
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 17:00:10
     */
    public function getNetfilterConfig ($host_id_a)
    {
        $return = Db::name('HostModuleRelation')->field('hmr.host_id,hlc.auto,hlc.allow,hlc.have_mac_addr')->alias('hmr')->join('HostLearnConfig hlc', 'hlc.host_id=hmr.host_id')->where('hmr.netfilter', 1)->whereIn('hmr.host_id', $host_id_a)->column('hlc.auto,hlc.allow,hlc.have_mac_addr', 'hmr.host_id');

        return $return;
    }

    /**
     * @name: 获取未进行匹配的 学习数据
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 17:04:27
     */
//    public function getUnmachedLearnData ()
//    {
        //TODO 此处为bug 分表后无法使用
//        $return = Db::name('HostLearnData')->field('id,host_id,protocols,src_ip,src_mac,dest_ip,dest_port,dest_mac,direction,have_mac_addr,view,last_view')->where('matched', 0)->limit($this->max_process_number)->select();
//        return $return;
//    }

    /**
     * @name: 添加fid 并且发送到客户端
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2019-12-16 17:54:55
     */
    public function addFidAndsendClient()
    {
        if (empty($this->clientRules))
        {
            return false;
        }
        $commonBase = new \app\common\service\BaseService();
        global $errCode;
        $errCode = $commonBase->CODE_SUCCESS;
        global $online_host;
        $online_host = [
            [
                'id' => $this->hostId,
            ],
        ];
        $this->modifyFid();
        file_put_contents('../runtime/autoRule.log', ' 发送至异步', FILE_APPEND);
        
        $commonBase->Async('Netfilter', 'addRules', [$this->clientRules]);
    }

    /**
     * @name: 命中日志提醒
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-01-08 16:21:04
     */
    public function hitLogRemind ()
    {
        if($this->newHit == true)
        {
            Db::name('HostLearnConfig')->where('host_id', $this->hostId)->update(['virtual_rule_hit_remind' => 1]);
        }
        
    }

    /**
     * @name: 检测是否存在netfilter
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-01-15 16:07:04
     */
    public function checkNetfilter ()
    {
        $table_a = Db::query('SHOW COLUMNS FROM ' . config('database.prefix') . 'host_module_relation' );

        if(empty($table_a))
        {
            echo '程序未初始化';
            die;
        }

        $field = array_column($table_a, 'Field');
        if(!array_search('netfilter', $field))
        {
            echo '未开启该模块';
            die;
        }
    }
}
